use std::{
    borrow::Borrow,
    convert::TryFrom,
    io::{Read, Seek, SeekFrom, Write},
};
// FileHeaders 16b
// magic 8 version 4 filenum 4
// 4knum 4 web 12
// md5 16
// fileinfo 16b 4kid 4 4kinstart 2 namelen 2 filelen 8
// .... no lim
// eg: kkbt0000 0001  0001

/// Shabby KV struct. Maybe it can use for config file.
///
/// Example
///
/// ```no_run
/// use kkbt::SKV;
/// fn main() {
///     // SKV::init("data/index", "data/data").unwrap();
///
///     let skv = SKV::config("data/index", "data/data");
///
///     skv.set("kkbt", Vec::from("我是恐咖兵糖1".as_bytes()));
///     skv.set("kkbt", Vec::from("".as_bytes()));
///     assert_eq!(skv.get_utf8("kkbt"),None);
///
///     skv.set("kkbt", Vec::from("我是恐咖兵糖3".as_bytes()));
///     assert_eq!(skv.get_utf8("kkbt"),Some("我是恐咖兵糖3".to_string()));
///
///     assert_eq!(skv.get_utf8("abcdefg"),None);
///
///    // kkbt::show_file_hex("data/index", 3).unwrap();
/// }

/// ```
#[derive(Debug, Clone, Eq, PartialEq, Copy)]
pub struct SKV<'a> {
    index_file: &'a str,
    data_file: &'a str,
}
impl<'a> SKV<'a> {
    /// create two files: index and data files
    pub fn init(index_file: &'a str, data_file: &'a str) -> std::io::Result<SKV<'a>> {
        std::fs::File::create(index_file)?;
        std::fs::File::create(data_file)?;
        Ok(Self {
            index_file,
            data_file,
        })
    }
    /// return struct SKV
    pub fn config(index_file: &'a str, data_file: &'a str) -> SKV<'a> {
        Self {
            index_file,
            data_file,
        }
    }
    // SKV set key:&str value:Vec<u8>
    pub fn set(self, key: &'a str, value: Vec<u8>) {
        let mut index_file = std::fs::OpenOptions::new()
            .read(true)
            .write(true)
            .append(true)
            .open(self.index_file)
            .unwrap();
        let mut data_file = std::fs::OpenOptions::new()
            .read(true)
            .write(true)
            .append(true)
            .open(self.data_file)
            .unwrap();
        let start = self.data_file_len() as u16;
        let fileinto = FileInfo::new(0u32, start, key.len() as u16, value.len() as u64);
        index_file.write(&fileinto.to_bs()).unwrap();
        data_file.write(key.as_bytes()).unwrap();
        data_file.write(&value).unwrap();
    }

    /// SKV get key:&str -> `Option<Vec<u8>>`
    pub fn get(&self, key: &'a str) -> Option<Vec<u8>> {
        let mut index_file = std::fs::OpenOptions::new()
            .read(true)
            .open(self.index_file)
            .unwrap();
        let mut buf = [0u8; 16];
        let mut file_info_vec = Vec::new();
        loop {
            let num = index_file.read(&mut buf).unwrap();
            if num != 16 {
                break;
            }
            file_info_vec.push(FileInfo::from_bs(*buf.borrow()));
        }
        for iter in file_info_vec.iter().rev() {
            let name = SKV::take(self.data_file, iter.start as u64, iter.namelen as u64);
            if String::from_utf8(name).unwrap() == key {
                let file = SKV::take(
                    self.data_file,
                    (iter.start + iter.namelen) as u64,
                    iter.filelen,
                );
                return Some(file);
            }
        }
        None
    }
    /// SKV get convent to utf8  key: &str -> `Option<String>`
    pub fn get_utf8(&self, key: &'a str) -> Option<String> {
        if self.get(key) == None || String::from_utf8(self.get(key).unwrap()).unwrap() == "" {
            return None;
        }
        Some(String::from_utf8(self.get(key).unwrap()).unwrap())
    }
    /// SKV 's data_file len ,use for next file start
    fn data_file_len(&self) -> u64 {
        let mut index_file = std::fs::OpenOptions::new()
            .read(true)
            .write(true)
            .append(true)
            .open(self.index_file)
            .unwrap();
        let mut buf = [0u8; 16];
        let mut sum_len = 0u64;
        loop {
            let num = index_file.read(&mut buf).unwrap();
            if num < 16 {
                break;
            }
            let fileinfo = FileInfo::from_bs(*buf.borrow());
            sum_len += fileinfo.namelen as u64 + fileinfo.filelen;
        }
        sum_len
    }
    /// from file take `Vec<u8>`
    fn take(path: &'a str, seek: u64, take: u64) -> Vec<u8> {
        let mut file = std::fs::File::open(path).unwrap();
        file.seek(SeekFrom::Start(seek)).unwrap();
        let mut handle = file.take(take);
        let mut buffer = [0u8; 4096];
        handle.read(&mut buffer).unwrap();
        buffer[..take as usize].to_vec()
    }
}

/// index file info struct
#[derive(Debug, PartialEq)]
struct FileInfo {
    b4kid: u32,   // 32位 4b 数据块个数
    start: u16,   //  4kb 块起始位置
    namelen: u16, // keylen
    filelen: u64, // valuelen
}
impl FileInfo {
    /// build FileInfo
    fn new(b4kid: u32, start: u16, namelen: u16, filelen: u64) -> Self {
        // assert!(b4kid > 0);
        Self {
            b4kid,
            start,
            namelen,
            filelen,
        }
    }
    /// convert FileInfo to bytes
    fn to_bs(&self) -> [u8; 16] {
        let ans23 = [self.start.to_be_bytes(), self.namelen.to_be_bytes()].concat();
        let ans123 = [self.b4kid.to_be_bytes(), ans23.try_into().unwrap()].concat();
        let ans1234 = [ans123, self.filelen.to_be_bytes().to_vec()].concat();
        let mut arr: [u8; 16] = [0; 16];
        for i in 0..16 {
            arr[i] = ans1234[i];
        }
        // println!("{:?}", arr);
        arr
    }
    /// convert bytes to FileInfo
    fn from_bs(byt: [u8; 16]) -> Self {
        Self::new(
            u32::from_be_bytes(<[u8; 4]>::try_from(&byt[0..4]).expect("Err Convent")),
            u16::from_be_bytes(<[u8; 2]>::try_from(&byt[4..6]).expect("Err Convent")),
            u16::from_be_bytes(<[u8; 2]>::try_from(&byt[6..8]).expect("Err Convent")),
            u64::from_be_bytes(<[u8; 8]>::try_from(&byt[8..16]).expect("Err Convent")),
        )
    }
}
